package pers.xiaojun.boot.module.system.service.oauth2;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import pers.xiaojun.boot.common.exception.BusinessException;
import pers.xiaojun.boot.module.system.constants.RedisKeyConstants;
import pers.xiaojun.boot.module.system.dao.mapper.oauth2.OAuth2RefreshTokenMapper;
import pers.xiaojun.boot.module.system.dao.redis.TokenRedisDAO;
import pers.xiaojun.boot.module.system.dao.entity.oauth2.OAuth2ClientDO;
import pers.xiaojun.boot.module.system.dao.entity.oauth2.OAuth2RefreshTokenDO;
import pers.xiaojun.boot.module.system.dao.entity.user.AdminUserDO;
import pers.xiaojun.boot.module.system.service.oauth2.dto.OAuth2ClientDTO;
import pers.xiaojun.boot.module.system.service.oauth2.dto.OAuth2TokenDTO;
import pers.xiaojun.boot.tenant.core.context.TenantContextHolder;

import java.time.LocalDateTime;
import java.util.List;

import static pers.xiaojun.boot.module.system.constants.BusinessCodeConstants.OAUTH2_REFRESH_TOKEN_CLIENT_NOT_MATCH;
import static pers.xiaojun.boot.module.system.constants.BusinessCodeConstants.OAUTH2_REFRESH_TOKEN_INVALID;

/**
 * Token服务实现类
 *
 * @author xiaojun
 * @since 2025-10-08
 */
@Service
@RequiredArgsConstructor
public class OAuth2TokenServiceImpl implements OAuth2TokenService {

    private final OAuth2RefreshTokenMapper oAuth2RefreshTokenMapper;
    private final TokenRedisDAO tokenRedisDAO;
    private final OAuth2ClientService oAuth2ClientService;

    @Override
    public OAuth2TokenDTO createAccessToken(Long userId, String clientId, String secret, List<String> scopes) {
        // 校验客户端
        OAuth2ClientDO clientDO = oAuth2ClientService.checkOAuth2Client(clientId, secret);

        // 获取刷新令牌
        OAuth2RefreshTokenDO oAuth2RefreshToken = createOAuth2RefreshToken(userId, clientDO, scopes);

        return createOAuth2AccessToken(userId, oAuth2RefreshToken, clientDO, scopes);
    }

    @Override
    public OAuth2TokenDTO refreshAccessToken(String refreshToken, String clientId) {
        // 校验客户端
        OAuth2ClientDO client = oAuth2ClientService.checkOAuth2Client(clientId, null);
        // 获取刷新令牌信息
        OAuth2RefreshTokenDO refreshTokenDO = oAuth2RefreshTokenMapper.selectByRefreshToken(refreshToken);

        // 刷新令牌不存在
        if (refreshTokenDO == null) {
            throw new BusinessException(OAUTH2_REFRESH_TOKEN_INVALID);
        }

        // 刷新令牌过期
        if (LocalDateTime.now().isAfter(refreshTokenDO.getExpiresTime())) {
            oAuth2RefreshTokenMapper.deleteByRefreshToken(refreshToken);
            throw new BusinessException(OAUTH2_REFRESH_TOKEN_INVALID);
        }

        // 校验刷新令牌和客户端是否匹配
        if (!StrUtil.equals(refreshTokenDO.getClientId(), clientId)) {
            throw new BusinessException(OAUTH2_REFRESH_TOKEN_CLIENT_NOT_MATCH);
        }

        return createOAuth2AccessToken(refreshTokenDO.getUserId(), refreshTokenDO, client, refreshTokenDO.getScopes());
    }

    @Override
    public OAuth2TokenDTO checkAccessToken(String token) {
        return tokenRedisDAO.get(token);
    }

    @Override
    public void removeAccessToken(String token) {
        OAuth2TokenDTO oAuth2Token = tokenRedisDAO.get(token);
        // 删除刷新Token
        oAuth2RefreshTokenMapper.deleteByRefreshToken(oAuth2Token.getRefreshToken());
        // 删除访问Token
        tokenRedisDAO.delete(StrUtil.format(RedisKeyConstants.Token.USER_TOKEN, token));
    }

    /**
     * 生成访问令牌
     *
     * @param userId       用户Id
     * @param refreshToken 刷新令牌信息
     * @param client       客户端信息
     * @return 访问令牌
     */
    private OAuth2TokenDTO createOAuth2AccessToken(Long userId, OAuth2RefreshTokenDO refreshToken, OAuth2ClientDO client, List<String> scopes) {
        OAuth2TokenDTO oAuth2TokenDTO = OAuth2TokenDTO.builder()
                .accessToken(generateAccessToken())
                .refreshToken(refreshToken.getRefreshToken())
                .scopes(scopes)
                .userId(userId)
                .tenantId(TenantContextHolder.getTenantId())
                .clientId(client.getClientId())
                .expiresTime(LocalDateTime.now().plusSeconds(client.getAccessTokenValiditySeconds()))
                .build();
        // 缓存Token
        tokenRedisDAO.save(oAuth2TokenDTO);
        return oAuth2TokenDTO;
    }

    /**
     * 生成刷新令牌
     *
     * @param userId 用户Id
     * @param client 客户端信息
     * @param scopes 授权空间
     * @return 刷新令牌实体 {@link OAuth2RefreshTokenDO}
     */
    private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, OAuth2ClientDO client, List<String> scopes) {
        OAuth2RefreshTokenDO oAuth2RefreshTokenDO = new OAuth2RefreshTokenDO()
                .setRefreshToken(generateRefreshToken())
                .setUserId(userId)
                .setUserType(2)
                .setClientId(client.getClientId())
                .setExpiresTime(LocalDateTime.now().plusSeconds(client.getRefreshTokenValiditySeconds()))
                .setScopes(scopes);
        oAuth2RefreshTokenMapper.insert(oAuth2RefreshTokenDO);
        return oAuth2RefreshTokenDO;
    }

    /**
     * 生成访问令牌
     */
    private static String generateAccessToken() {
        return IdUtil.fastSimpleUUID();
    }

    /**
     * 生成刷新令牌
     */
    private static String generateRefreshToken() {
        return IdUtil.fastSimpleUUID();
    }

}
